/**************************************************
*
* jCustomSearch.js
* A JavaScript wrapper for the Google Custom Search API
* 
* Copyright (c) 2010, Jeremy R. Geerdes
* You are free to copy and use this sample under the terms of the Apache License, Version 2.0.
* For a copy of the license, view http://www.apache.org/licenses/LICENSE-2.0
*
* This script is offered AS-IS, with absolutely no warranty of any kind.
* 
**************************************************/




(function(){
 var ERROR_STRING = 'CRITICAL SYSTEM ALERT! ', // common strings and settings
  BASE_AJAX_URL = 'https://ajax.googleapis.com/ajax/services/search/',
  BASE_CSAPI_URL = 'https://www.googleapis.com/customsearch/v1',
  POWERED_BY_STRING = 'powered by',
  CSAPI_MAX_RESULTS = 101,
  AJAX_MAX_RESULTS = 64,
  DATE_MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
  DATE_MDY = 0,
  DATE_MINUTES_AGO = 'minutes ago',
  DATE_HOURS_AGO = 'hours ago',
  DATE_DAYS_AGO = 'days ago',
  SEARCH_FORM_HINT_STRING = 'What do you want?',
  SEARCH_FORM_BUTTON_TEXT = 'Search',
  SEARCH_FORM_CLEAR_BUTTON_TEXT = 'clear results',
  POWERED_BY_LOGO = 'http://www.google.com/uds/css/small-logo.png',
  SEARCH_CONTROL_SHOW_ONE_RESULT = 'show one result',
  SEARCH_CONTROL_SHOW_MORE_RESULT = 'show more results',
  SEARCH_CONTROL_SHOW_ALL_RESULT = 'show even more results';
 
 // load the CSS if appropriate
 if(!window.noGSearchCss){
  var style = createEl('link',null,null,{
   'type' : 'text/css',
   'rel' : 'stylesheet',
   'href' : 'http://www.google.com/uds/css/gsearch.css'
  });
  document.getElementsByTagName('head')[0].appendChild(style);
 }

 // this is the basic searcher which can be cloned and expanded for all others
 function BasicSearcher(){ 
  var options = this.options = {
   'v' : '1.0'
  };
 }
 BasicSearcher.prototype['setRestriction'] = function(restriction, value){
  if(typeof(value) !== null){
   this.options[restriction] = value;
  } else {
   delete(this.options[restriction]);
  }
 };
 BasicSearcher.prototype.processOptions = function(){
  var urlArgs = [];
  for(var i in this.options){
   urlArgs.push(i + '=' + encodeURIComponent(typeof(this.options[i]) == 'function' ? this.options[i].apply(this,[]) : this.options[i]));
  }
  return urlArgs;
 };
 BasicSearcher.prototype['setSearchStartingCallback'] = function(context, method, args){
  this.searchStartingCallback = createClosure(context, method, args);
 };
 BasicSearcher.prototype['setSearchCompleteCallback'] = function(context, method, args){
  this.searchCompleteCallback = createClosure(context, method, args);
 };
 BasicSearcher.prototype['setErrorCallback'] = function(context, method, args){
  this.errorHandler = createClosure(context, method, args);
 };
 BasicSearcher.prototype['setResultSetSize'] = function(rsz){
  this['setRestriction']('rsz', rsz);
 };
 BasicSearcher.prototype['getResultSetSize'] = function(){
  return this.options['rsz'];
 };
 BasicSearcher.prototype['clearResults'] = function(){
  this.results = null;
 };
 BasicSearcher.prototype['setUserDefinedLabel'] = function(label){
  this.label = label;
 };
 BasicSearcher.prototype['setUserDefinedClassSuffix'] = function(suffix){
  this.classSuffix = suffix;
 };
 BasicSearcher.prototype['setLinkTarget'] = function(target){
  this.linkTarget = target;
 };
 BasicSearcher.prototype['setNoHtmlGeneration'] = function(yesNo){
  this.noHtml = yesNo;
 };
 BasicSearcher.prototype['setQueryAddition'] = function(addition){
  this.queryAddition = addition;
 };
 BasicSearcher.prototype['gotoPage'] = function(page){
  if(this.currentQuery){
   var start = (page - 1) * (this.options['rsz'] || 4);
   this.execute(this.currentQuery, start);
  }
 };
 BasicSearcher.prototype['execute'] = function(query, start){
  var urlArgs = this.processOptions(),
   script;
  this.currentQuery = query;
  urlArgs.push('q=' + encodeURIComponent(query + (this.queryAddition ? ' ' + this.queryAddition : '') + (this.siteRestriction ? ' site:' + this.siteRestriction : '')));
  if(start){
   urlArgs.push('start=' + encodeURIComponent(start));
  }
  script = createScript(this.baseUrl + '?' + urlArgs.join('&'));
  if(typeof(this.searchStartingCallback)=='function'){
   this.searchStartingCallback();
  }
  document.getElementsByTagName('head')[0].appendChild(script);
 };
 BasicSearcher.prototype['setErrStr'] = function(errorString){
  this.errorString = errorString;
 };
 BasicSearcher.prototype['RAWcallback'] = function(){
  var result,
   responseData;
  if(arguments.length == 1){
   result = arguments[0];
  } else {
   result = {
    'responseData' : arguments[1],
    'responseStatus' : arguments[2],
    'responseDetails' : arguments[3]
   };
   return BasicSearcher.prototype['RAWcallback'].apply(searchers[arguments[0]], [result]);
  }
  responseData = result['responseData'];
  this['results'] = responseData['results'];
  this['cursor'] = responseData['cursor'];
  this['error'] = responseData['error'];
  if(responseData['error'] && typeof(this.errorHandler) == 'function'){
   this.errorHandler();
  }
  if(!this.noHtml && this['results']){
   for(var i=0; i<this['results'].length; i++){
    this['createResultHtml'](this['results'][i]);
   }
  }
  if(typeof(this.searchCompleteCallback) == 'function'){
   return this.searchCompleteCallback();
  }
 };
 BasicSearcher['getBranding'] = function(parentEl){
  var className = 'gsc-branding',
   el = createEl('table',className,[
    createEl('tbody',null,[
     createEl('tr',null,[
      createEl('td',className+'-user-defined'),
      createEl('td',className+'-text',[
       createEl(null,className+'-text',[createTextNode(POWERED_BY_STRING)])
      ]),
      createEl('td',className+'-img',[
       createEl('a',className+'-img',[
        createEl('img',className+'-img',null,{
         'src' : POWERED_BY_LOGO
        })
       ],{
        'href' : 'http://www.google.com'
       })
      ])
     ])
    ])
   ],{
    'cellpadding' : 0,
    'cellspacing' : 0
   });
  if(parentEl){
   parentEl.appendChild(el);
  }
  return el;
 };
 BasicSearcher.RAWcallback = function(context, responseData, responseStatus, responseDetails){
  BasicSearcher['searchers'][context].RAWcallback(response);
 };
 var searchers = BasicSearcher['searchers'] = [];

 
 // individual searchers
 function CustomSearch(opt_options){ // Custom Search API wrapper NOTE: You must provide a cx/cref and apiKey option
  for(var i in opt_options){
   this.options[i] = opt_options[i]
  }
  this.baseUrl = BASE_CSAPI_URL;
  if(!this.options['cx'] && !this.options['cref']){
   throw(ERROR_STRING+'No cx or cref provided.');
  }
  if(!this.options['key']){
   throw(ERROR_STRING + 'No CSAPI key provided. To obtain one, visit http://code.google.com/apis/console');
  }
  this.context = searchers.length;
  searchers.push(this);
  this.options['callback'] = 'window.jeremy.google.search.CustomSearch.searchers[' + this.context + '].RAWcallback';
  this.label = 'CSE';
  this.className = 'web';
 }
 cloneClass(CustomSearch, BasicSearcher);
 CustomSearch.prototype['RAWcallback'] = function(response){ // this method normalizes CSAPI response data to the AJAX API standard and passes it to the BasicSearcher RAWcomplete
  var reqInfo = response['queries'] && response['queries']['request'] ? response['queries']['request'][0] : {},
   responseData = {
    'results' : response['items'],
    'cursor' : {
     'estimatedResultCount' : reqInfo['totalResults'],
     'moreResultsUrl' : null,
     'currentPageIndex' : (reqInfo['startIndex'] - 1) / reqInfo['count'],
     'pages' : []
    },
    'error' : response['error']
   },
   normalizedResponse = {
    'responseData' : responseData,
    'responseDetails' : null,
    'responseStatus' : 200
   };
  for(var i=1; i<CSAPI_MAX_RESULTS && i<reqInfo['totalResults']; i+=reqInfo['count']){
   responseData['cursor']['pages'].push({
    'start' : i.toString(),
    'label' : ((i - 1) / reqInfo['count']) + 1
   });
  }
  BasicSearcher.prototype['RAWcallback'].apply(this,[normalizedResponse]);
 };
 CustomSearch.prototype['gotoPage'] = function(page){
  if(this.currentQuery){
   var start = (page-1) * (this.options['count'] || 10) + 1;
   this.execute(this.currentQuery, start);
  }
 };
 CustomSearch.prototype['setResultSetSize'] = function(rsz){
  this['setRestriction']('count', rsz);
 };
 CustomSearch.prototype['getResultSetSize'] = function(){
  return this.options['count'];
 };
 CustomSearch.prototype['createResultHtml'] = function(result){
  var el = result.html = createEl(null,'gs-result gs-webResult',[
   createEl(null,'gs-title',[
    createEl('a','gs-title',[result['htmlTitle']],{
     'href' : result['link'],
     'title' : result['title'],
     'target' : this.linkTarget || undefined
    })
   ]),
   createEl(null,'gs-snippet',[this.options['usePlainTextSnippet'] ? result['snippet'] : result['htmlSnippet']]),
   createEl(null,'gs-visibleUrl gs-visibleUrl-long',[result['link']]),
   createEl(null,'gs-visibleUrl gs-visibleUrl-short',[result['displayLink']])
  ]);
  return el;
 };
 CustomSearch['searchers'] = searchers;
 
 // ajax apis searchers  
 function NewsSearch(opt_options){ // search for news results via ajax api
  for(var i in opt_options){
   this.options[i] = opt_options[i];
  }
  this.baseUrl = BASE_AJAX_URL + 'news';
  this.context = searchers.length;
  searchers.push(this);
  this.options['callback'] = 'window.jeremy.google.search.NewsSearch.prototype.RAWcallback';
  this.options['context'] = this.context;
  this.label = 'News';
  this.className = 'news';
 }
 cloneClass(NewsSearch, BasicSearcher);
 NewsSearch.prototype['createResultHtml'] = function(result){
  var el = result.html = createEl(null,'gs-result '+'gs-newsResult',[
   createEl(null,'gs-title',[
    createEl('a','gs-title',[result['title']],{
     'href' : result['url'],
     'title' : result['titleNoFormatting'],
     'target' : this.linkTarget || undefined
    })
   ]),
   createEl(null,'gs-publisher',[result['publisher']]),
   createEl(null,'gs-publishedDate',[' - ' + this.getPublishedDate(result['publishedDate'])]),
   createEl(null,'gs-relativePublishedDate',[' - ' + this.getRelativePublishedDate(result['publishedDate'])]),
   createEl(null,'gs-snippet',[result['content']]),
   createEl(null,'gs-watermark',[])
  ]);
  return el;
 };
 NewsSearch.prototype.getPublishedDate = function (dateStr){
  var pubDate = new Date(dateStr),
   month = DATE_MONTHS[pubDate.getMonth()];
   day = pubDate.getDate();
  return (this.MDY ? month + ' ' + day + ',' : day + ' ' + month) + ' ' + pubDate.getFullYear();
 };
 NewsSearch.prototype.getRelativePublishedDate = function(dateStr){
  var pubDate = (new Date(dateStr)).getTime(),
   now = (new Date).getTime(),
   difference = (now - pubDate) / 1000;
  if(difference < 120) {
   return 2 + ' ' + DATE_MINUTES_AGO;
  } else if(difference < 3600) {
   return Math.round(difference / 60) + ' ' + DATE_MINUTES_AGO;
  } else if(difference < 86400) {
   return Math.round(difference / 3600) + ' ' + DATE_HOURS_AGO;
  } else if(difference < 864000) {
   return Math.round(difference / 86400) + ' ' + DATE_DAYS_AGO;
  } else {
   return this.getPublishedDate(dateStr);
  }
 }
 
 function ImageSearch(opt_options){ // search for image results via ajax api
  for(var i in opt_options){
   this.options[i] = opt_options[i];
  }
  this.baseUrl = BASE_AJAX_URL + 'images';
  this.context = searchers.length;
  searchers.push(this);
  this.options['callback'] = 'window.jeremy.google.search.ImageSearch.prototype.RAWcallback';
  this.options['context'] = this.context;
  this.label = 'Images';
  this.className = 'images';
 }
 cloneClass(ImageSearch, BasicSearcher);
 ImageSearch.prototype['createResultHtml'] = function(result){
  var el = result.html = createEl(null,'gs-result '+'gs-imageResult',[
   createEl(null,'gs-image-box',[
    createEl('a','gs-image',[
     createEl('img','gs-image',null,{
      'src' : result['tbUrl'],
      'alt' : result['titleNoFormatting'],
      'target' : this.linkTarget || undefined
     })
    ],{
     'href' : result['url'],
     'title' : result['titleNoFormatting'],
     'target' : this.linkTarget || undefined
    })
   ]),
   createEl(null,'gs-text-box',[
    createEl(null,'gs-snippet',[result['content']]),
    createEl(null,'gs-size',[result['width'] + ' x ' + result['height']]),
    createEl(null,'gs-visibleUrl',[
     createEl('a','gs-visibleUrl',[result['visibleUrl']],{
      'href' : result['url'],
      'title' : result['visibleUrl'],
      'target' : this.linkTarget || undefined
     })
    ])
   ]),
   createEl(null,'gs-watermark',[
   ])
  ]);
  return el;
 };

 function PatentSearch(opt_options){ // search for patent results via ajax api
  for(var i in opt_options){
   this.options[i] = opt_options[i];
  }
  this.baseUrl = BASE_AJAX_URL + 'patent';
  this.context = searchers.length;
  searchers.push(this);
  this.options['callback'] = 'window.jeremy.google.search.PatentSearch.prototype.RAWcallback';
  this.options['context'] = this.context;
  this.label = 'Patents';
  this.className = 'patent';
 }
 cloneClass(PatentSearch, BasicSearcher);
 PatentSearch.prototype['setResultOrder'] = function(orderBy){
  this['setRestriction']('scoring', orderBy);
 };
 PatentSearch.prototype['createResultHtml'] = function(result){
  var el = result.html = createEl(null,'gs-result '+'gs-patentResult',[
   createEl('table',null,[
    createEl('tbody',null,[
     createEl('tr',null,[
      createEl('td','gs-image-box',[
       createEl(null,'gs-patent-image',[
        createEl('a','gs-image',[
         createEl('img','gs-image',null,{
          'src' : result['tbUrl'],
          'alt' : result['titleNoFormatting']
         })
        ],{
         'href' : result['url'],
         'title' : result['titleNoFormatting'],
         'target' : this.linkTarget || undefined
        })
       ])
      ]),
      createEl('td','gs-text-box',[
       createEl(null,'gs-text-box',[
        createEl(null,'gs-title',[
         createEl('a','gs-title',[result['title']],{
          'href' : result['url'],
          'title' : result['titleNoFormatting'],
          'target' : this.linkTarget || undefined
         })
        ]),
        createEl(null,'gs-patent-info gs-metadata',[
         createEl(null,'gs-patent-number',[result['patentNumber']]),
         createEl(null,'gs-publishedDate',[' - ' + this.getApplicationDate(result['applicationDate'])])
        ]),
        createEl(null,'gs-snippet',[result['content']])
       ])
      ])
     ])
    ])
   ],{
    'cellspacing' : 0,
    'cellpadding' : 0
   }),
   createEl(null, 'gs-watermark', [
   ])
  ]);
  return el;
 };
 PatentSearch.prototype.getApplicationDate = NewsSearch.prototype.getPublishedDate;
 
 function VideoSearch(opt_options){ // search for video results via ajax api
  for(var i in opt_options){
   this.options[i] = opt_options[i];
  }
  this.baseUrl = BASE_AJAX_URL + 'video';
  this.context = searchers.length;
  searchers.push(this);
  this.options['callback'] = 'window.jeremy.google.search.VideoSearch.prototype.RAWcallback';
  this.options['context'] = this.context;
  this.label = 'Videos';
  this.className = 'video';
 }
 cloneClass(VideoSearch, BasicSearcher);
 VideoSearch.prototype['setResultOrder'] = function(orderBy){
  this['setRestriction']('scoring', orderBy);
 };
 VideoSearch.prototype['createResultHtml'] = function(result){
  var el = result.html = createEl(null,'gs-result '+'gs-videoResult',[
   createEl('table',null,[
    createEl('tbody',null,[
     createEl('tr',null,[
      createEl('td','gs-image-box',[
       createEl(null,'gs-image-box',[
        createEl('a','gs-image',[
         createEl('img','gs-image',[],{
          'src' : result['tbUrl'],
          'alt' : result['titleNoFormatting']
         })
        ],{
         'href' : result['url'],
         'title' : result['titleNoFormatting'],
         'target' : this.linkTarget || undefined
        })
       ])
      ]),
      createEl('td','gs-text-box',[
      ])
     ])
    ])
   ],{
    'cellpadding' : 0,
    'cellspacing' : 0
   }),
   createEl(null,'gs-watermark',[
   ])
  ]);
  return el;
 };

 function BlogSearch(opt_options){ // search for video results via ajax api
  for(var i in opt_options){
   this.options[i] = opt_options[i];
  }
  this.baseUrl = BASE_AJAX_URL + 'blogs';
  this.context = searchers.length;
  searchers.push(this);
  this.options['callback'] = 'window.jeremy.google.search.BlogSearch.prototype.RAWcallback';
  this.options['context'] = this.context;
  this.label = 'Blog';
  this.className = 'blog';
 }
 cloneClass(BlogSearch, BasicSearcher);
 BlogSearch.prototype['setResultOrder'] = function(orderBy){
  this['setRestriction']('scoring', orderBy);
 };
 BlogSearch.prototype['setSiteRestriction'] = function(site){
  this.siteRestriction = site;
 };
 BlogSearch.prototype['createResultHtml'] = function(result){
  var el = result.html = createEl(null,'gs-result '+'gs-blogResult',[
   createEl(null,'gs-title',[
    createEl('a','gs-title',[result['title']],{
     'href' : result['url'],
     'title' : result['titleNoFormatting'],
     'target' : this.linkTarget || undefined
    })
   ]),
   createEl(null,'gs-publishedDate',[this.getPublishedDate(result['publishedDate'])]),
   createEl(null,'gs-relativePublishedDate',[this.getRelativePublishedDate(result['publishedDate'])]),
   createEl(null,'gs-snippet',[result['content']]),
   createEl(null,'gs-visibleUrl',[result['blogUrl']]),
   createEl(null,'gs-watermark',[
   
   ])
  ]);
  return el;
 };
 BlogSearch.prototype.getPublishedDate = NewsSearch.prototype.getPublishedDate;
 BlogSearch.prototype.getRelativePublishedDate = NewsSearch.prototype.getPublishedDate;

 
 
 // Tabbed Search Control. I'm not screwing around with the stacked control because I hate it. :D
 function SearchControl(){
  this['searchers'] = [];
 }
 SearchControl.prototype['getContainer'] = function(){
  return this.container;
 };
 SearchControl.prototype['addSearcher'] = function(searcher, opt_options){
  var searcherObj,
   counter,
   resultsDiv,
   expansionDiv;
  searcherObj = {
   searcher : searcher,
   tabDiv : createEl(null, 'gsc-tabHeader gsc-tabhInactive', [searcher.label]),
   resultsRoot : createEl(null, 'gsc-resultsRoot gsc-tabData gsc-tabdInactive', [
    createEl('table','gsc-resultsHeader',[
     createEl('tbody',null,[
      createEl('tr',null,[
       createEl('td','gsc-twiddleRegionCell gsc-twiddle-opened',[
        createEl(null,'gsc-twiddle',[
         createEl(null,'gsc-title',[searcher.label])
        ]),
        counter = createEl(null,'gsc-stats'),
        createEl(null,'gsc-results-selector gsc-more-results-active',[
         createEl(null,'gsc-result-selector ' + 'gsc-one-result',[' '],{'title' : SEARCH_CONTROL_SHOW_ONE_RESULT}),
         createEl(null,'gsc-result-selector ' + 'gsc-more-results',[' '],{'title' : SEARCH_CONTROL_SHOW_MORE_RESULT}),
         createEl(null,'gsc-result-selector ' + 'gsc-all-results',[' '],{'title' : SEARCH_CONTROL_SHOW_ALL_RESULT})
        ])
       ])
      ])
     ])
    ]),
    resultsDiv = createEl(null,'gsc-results ' + 'gsc-'+searcher.className+'Result',[
     expansionDiv = createEl(null,'gsc-expansionArea')
    ])
   ])
  };
  searcherObj.resultsDiv = resultsDiv;
  searcherObj.expansionDiv = expansionDiv;
  searcherObj.tabDiv.onclick = createClosure(this, SearchControl.prototype['makeActiveTab'], [searcherObj]);
  searcher['setSearchCompleteCallback'](this, SearchControl.prototype.callback, [searcherObj]);
  this['searchers'].push(searcherObj);
  if(this.container){ // if the control is already drawn, let's go ahead and add the tab to the DOM
   this.addTab(searcherObj);
  }
 };
 SearchControl.prototype.addTab = function(searcherObj){
  this.tabsArea.appendChild(searcherObj.tabDiv);
  this.tabsArea.appendChild(createEl('span','gs-spacer',[' ']));
  this.resultsArea.appendChild(searcherObj.resultsRoot);
 };
 SearchControl.prototype['removeSearcher'] = function(searcherObj){
  var whichSearcher;
  if(typeof(searcherObj) == 'number' && this['searchers'][searcherObj]){
   whichSearcher = searcherObj;
   searcherObj = this['searchers'][searcherObj];
  } else {
   for(var i=0; i<this['searchers'].length; i++){
    if(searcherObj === this['searchers'][i] || searcherObj === this['searchers'][i].searcher){
     searcherObj = this['searchers'][i];
     whichSearcher = i;
     break;
    }
   }
  }
  if(this.container){ // if the control is drawn, we need to remove the various els from the dom
   this.tabsArea.removeChild(searcherObj.tabDiv.nextSibling);
   this.tabsArea.removeChild(searcherObj.tabDiv);
   this.resultsArea.removeChild(searcherObj.resultsRoot);
  }
  this['searchers'] = this['searchers'].splice(whichSearcher, 1);
  delete(searcherObj);
 };
 SearchControl.prototype['draw'] = function(parentEl, drawOpts){
  if(!drawOpts){
   drawOpts = {};
  }
  parentEl = this.container = typeof(parentEl) == 'string' ? document.getElementById(parentEl) : parentEl;
  var control = createEl(null, 'gsc-control', [
   (this.searchForm = new SearchForm(1, null, drawOpts['searchFormOptions'])).form,
   this.tabsArea = createEl(null,'gsc-tabsAreaInvisible'),
   this.resultsArea = createEl(null,'gsc-resultsbox-invisible')
  ]);
  if(drawOpts['searchFormRoot']){
   drawOpts['searchFormRoot'].appendChild(this.searchForm.form)
  }
  this.input = this.searchForm.input;
  this.searchForm['setOnSubmitCallback'](this, SearchControl.prototype.execute);
  parentEl.appendChild(control);
  if(this['searchers'].length > 0){
   for(var i=0; i<this['searchers'].length; i++){
    this.addTab(this['searchers'][i]);
   }
   this['makeActiveTab'](0);
  }
 };
 SearchControl.prototype['execute'] = function(query){
  query = this['input'].value = typeof(query)=='string' ? query : this['input'].value;
  this['clearAllResults']();
  this.searchersDone = 0;
  if(this.searchStartingCallback){
   this.searchStartingCallback();
  }
  for(var i=0; i<this['searchers'].length && query != ''; i++){
   if(this.searcherStartingCallback){
    this.searcherStartingCallback(this['searchers'][i].searcher);
   }
   this['searchers'][i].searcher['execute'](query)
  }
  return false;
 };
 SearchControl.prototype['clearAllResults'] = function(){
  var searchers = arguments > 0 ? arguments : this['searchers'];
  for(var i=0; i<searchers.length; i++){
   var searcher = searchers[i];
   while(searcher.resultsDiv.firstChild !== searcher.expansionDiv){
    searcher.resultsDiv.removeChild(searcher.resultsDiv.firstChild);
   }
   rmChildren(searcher.expansionDiv);
  }
 };
 SearchControl.prototype['toggle'] = function(searcher){
 };
 SearchControl.prototype['setResultSetSize'] = function(rsz){
  for(var i=0; i<this['searchers'].length; i++){
   var searcher = this['searchers'][i].searcher;
   searcher['setResultSetSize'](rsz);
  }
 };
 SearchControl.prototype['setLinkTarget'] = function(target){
  for(var i=0; i<this['searchers'].length; i++){
   var searcher = this['searchers'][i].searcher;
   searcher['setLinkTarget'](target);
  }
 };
 SearchControl.prototype['makeActiveTab'] = function(searcherObj){ // this 
  var inactiveR = /(in)?active/ig;
  for(var i=0; i<this['searchers'].length; i++){
   var searcher = this['searchers'][i];
   if(searcher === searcherObj || searcher.searcher === searcherObj || i === searcherObj){
    searcher.resultsRoot.className = searcher.resultsRoot.className.replace(inactiveR, 'Active');
    searcher.tabDiv.className = searcher.tabDiv.className.replace(inactiveR, 'Active');
    this.activeTab = i;
   }else{
    searcher.resultsRoot.className = searcher.resultsRoot.className.replace(inactiveR, 'Inactive');
    searcher.tabDiv.className = searcher.tabDiv.className.replace(inactiveR, 'Inactive');
   }
  }
 };
 SearchControl.prototype['setSearcherStartingCallback'] = function(context, method, args){ // this establishes a function to be called for each searcher as it's executed. The function will get the searcher itself as an arg
  this.searcherStartingCallback = createClosure(context, method, args);
 };
 SearchControl.prototype['setSearcherCompleteCallback'] = function(context, method, args){ // this establishes a function to be called for each searcher as it is finished. The function will get the searcher itself as an arg
  this.searcherCompleteCallback = createClosure(context, method, args);
 };
 SearchControl.prototype['setSearchStartingCallback'] = function(context, method, args){ // this establishes a function to be called once before a search is executed.
  this.searchStartingCallback = createClosure(context, method, args);
 };
 SearchControl.prototype['setSearchCompleteCallback'] = function(context, method, args){ // this establishes a function to be called once when all the searchers have finished.
  this.searchCompleteCallback = createClosure(context, method, args);
 };
 SearchControl.prototype['findResults'] = function(){ // find a tab that has results
  var loopedAlready;
  for(var i=this.activeTab; !loopedAlready || i != this.activeTab; i++){
   if(i >= this['searchers'].length){
    i %= this['searchers'].length;
    loopedAlready = 1;
   }
   if(this['searchers'][i].searcher['results'].length > 0){
    this['makeActiveTab'](i);
    break;
   }
  }
 };
 SearchControl.prototype.callback = function(searcherObj){ // this is the callback to process the results of a searcher and display results
  var searcher = searcherObj.searcher,
   r = /invisible/i,
   pagesEl = createEl(null,'gsc-cursor');
  for(var i=0; searcher['results'] && i<searcher['results'].length; i++){
   var result = searcher['results'][i],
    resultEl = createEl(null,'gsc-result '+'gsc-' + searcher.className + 'Result',[result.html]);
   if(i == 0){ // if this is the first result, we slap it into the results div in front of the expansion area
    searcherObj.resultsDiv.insertBefore(resultEl, searcherObj.expansionDiv);
   }else{
    searcherObj.expansionDiv.appendChild(resultEl);
   }
  }
  for(var i=0; searcher['cursor'] && searcher['cursor']['pages'] && i<searcher['cursor']['pages'].length; i++){
   pagesEl.appendChild(createEl(null,'gsc-cursor'+'-page' + (i==searcher['cursor']['currentPageIndex'] ? ' gsc-cursor'+'-current-page' : ''),[
    searcher['cursor']['pages'][i]['label'].toString()
   ],{
    'onclick' : createClosure(this, function(i){
     this['clearAllResults'](searcher);
     searcher['gotoPage'](searcher['cursor']['pages'][i]['label']);
    }, [i])
   }));
  }
  searcherObj.expansionDiv.appendChild(createEl(null,'gsc-cursor'+'-box',[pagesEl]));
  this.tabsArea.className = this.tabsArea.className.replace(r, '');
  this.resultsArea.className = this.resultsArea.className.replace(r, 'visible');
  if(this.searcherCompleteCallback){
   this.searcherCompleteCallback(searcher);
  }
  this.searchersDone++;
  if(this.searchersDone == this['searchers'].length && this.searchCompleteCallback){
   this.searchCompleteCallback();
  }
 };
 
 
 // Search Form
 function SearchForm(enableClear, parentEl, opt_options){
  if(!opt_options){
   opt_options = {};
  }
  var form = this.form = createEl('form','gsc-search-box',[
   createEl('table','gsc-search-box',[
    createEl('tbody',null,[
     createEl('tr',null,[
      createEl('td','gsc-input',[
       this.input = createEl('input','gsc-input',null,{
        'type' : 'text',
        'autocomplete' : 'off',
        'size' : '10',
        'name' : 'search',
        'title' : 'search',
        'onfocus' : createClosure(this, SearchForm.prototype.toggleHint),
        'onblur' : createClosure(this, SearchForm.prototype.toggleHint)
       })
      ]),
      createEl('td','gsc-search-button',[
       createEl('input','gsc-search-button',null,{
        'type' : 'submit',
        'value' : opt_options['buttonText'] || SEARCH_FORM_BUTTON_TEXT,
        'title' : opt_options['buttonText'] || SEARCH_FORM_BUTTON_TEXT
       })
      ]),
      (enableClear ? createEl('td','gsc-clear-button',[
       createEl(null,'gsc-clear-button',[
        ' '
       ],{
        'title' : opt_options['clearButtonText'] || SEARCH_FORM_CLEAR_BUTTON_TEXT,
        'onclick' : createClosure(this, function(){form.reset()})
       })
      ]) : null)
     ])
    ])
   ]),
   BasicSearcher['getBranding']()
  ],{
   'onsubmit' : createClosure(this, function(){if(this.onSubmitCallback){return this.onSubmitCallback()}}),
   'onreset' : createClosure(this, function(){if(this.onClearCallback){return this.onClearCallback()}})
  });
  if(parentEl){
   (typeof(parentEl) == 'string' ? document.getElementById(parentEl) : parentEl).appendChild(form);
  }
 }
 SearchForm.prototype.toggleHint = function(e){
  var input = this.input;
  if(input.value == input.defaultValue){
   e.value = '';
  } else if(input.value == ''){
   e.value = e.defaultValue;
  }
 }
 SearchForm.prototype['setOnSubmitCallback'] = function(context, method, args){
  this.onSubmitCallback = createClosure(context, method, args);
 };
 SearchForm.prototype['setOnClearCallback'] = function(context, method, args){
  this.onClearCallback = createClosure(context, method, args);
 };
 
 
 
 // utility functions 
 function cloneClass(clonedClass, baseClass){ // this little function makes clonedClass into a clone of baseClass
  var clone = function(){};
  clone.prototype = new baseClass;
  clonedClass.prototype = new clone;
 }
 
 function createClosure(context, method, args){ // method closure, lisbakke. method closure.
  return function(){
   return method.apply(context, (args ? args : []).concat(arguments));
  }
 }
 
 function createScript(url){ // create a script element
  var script = document.createElement('script'),
   ol = function(){ // onload listener that will magically remove the script from the doc head after it's loaded
    script.onload = null;
    script.parentNode.removeChild(script);
    delete(script);
   },
   orsc = function(){ // because only Mozilla supports an onload listener for script els, this is the onreadystatechange listener for everything else
    if(script.readyState == 'loaded' || script.readyState == 'complete'){
     script.onreadystatechange = null;
     ol();
    }
   };
  script.type = 'text/javascript';
  script.src = url;
  if(navigator.product == 'Gecko'){
   script.onload = ol;
  }else{
   script.onreadystatechange = orsc;
  }
  return script;
 }
 
 function createEl(tagName, className, children, attribs, styles){ // create an html element with the appropriate stuff. All arguments are optional. If you supply none, a simple div will be returned.
  var el = document.createElement(tagName || 'div');
  if(className){
   el.className = className;
  }
  if(children){
   for(var i=0; i<children.length; i++){
    if(typeof(children[i])=='string'){
     el.innerHTML += children[i];
    } else {
     try{
      el.appendChild(children[i]);
     } catch(err) {
     }
    }
   }
  }
  if(attribs){
   for(i in attribs){
    if(i.match(/^on/)){
     el[i] = attribs[i];
    } else {
     el.setAttribute(i, attribs[i]);
    }
   }
  }
  if(styles){
   for(var i in styles){
    el.style[i] = styles[i];
   }
  }
  return el;
 }
 
 function createTextNode(str){ // create a text node
  return document.createTextNode(str);
 }
 
 function rmChildren(el){ // remove all the children from the given el
  while(el.firstChild){
   el.removeChild(el.firstChild);
  }
 }

 
 // export stuff
 if(!window['jeremy']){
  window['jeremy'] = {};
 }
 if(!window['jeremy']['google']){
  window['jeremy']['google'] = {};
 }
 var s = window['jeremy']['google']['search'] = window['jeremy']['google']['search'] || {}; // sorry. I'm lazy.
 s['CustomSearch'] = CustomSearch;
 s['NewsSearch'] = NewsSearch;
 s['ImageSearch'] = ImageSearch;
 s['BlogSearch'] = BlogSearch;
 s['VideoSearch'] = VideoSearch;
 s['PatentSearch'] = PatentSearch;
 s['SearchForm'] = SearchForm;
 s['SearchControl'] = SearchControl;
})()